/**
 * Copyright 2019 吉鼎科技.

 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.web.task.support;

import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.Sessions;
import org.zkoss.zk.ui.WebApps;
import org.zkoss.zk.ui.sys.DesktopCache;
import org.zkoss.zk.ui.sys.DesktopCtrl;
import org.zkoss.zk.ui.sys.WebAppCtrl;

import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @since 2.0.0 <br/>
 */
public abstract class LongOperation implements Runnable {

	private String desktopId;

	private DesktopCache desktopCache;

	private Thread thread;

	private AtomicBoolean cancelled = new AtomicBoolean(false);

	protected abstract void execute() throws InterruptedException;

	protected void onFinish() {
	};

	protected void onCancel() {
	};

	protected void onException(RuntimeException exception) {
	};

	protected void onCleanup() {
	};

	public final void cancel() {
		cancelled.set(true);
		thread.interrupt();
	}

	public final boolean isCancelled() {
		return cancelled.get();
	}

	protected final void activate() throws InterruptedException {
		Executions.activate(getDesktop());
	}

	protected final void deactivate() {
		Executions.deactivate(getDesktop());
	}

	protected final void checkCancelled() throws InterruptedException {
		if (Thread.currentThread() != this.thread) {
			throw new IllegalStateException(
					"this method can only be called in the worker thread (i.e. during execute)");
		}
		boolean interrupted = Thread.interrupted();
		if (interrupted || cancelled.get()) {
			cancelled.set(true);
			throw new InterruptedException();
		}
	}

	public final void start() {
		this.desktopId = Executions.getCurrent().getDesktop().getId();
		this.desktopCache = ((WebAppCtrl) WebApps.getCurrent())
				.getDesktopCache(Sessions.getCurrent());
		enableServerPushForThisTask();
		thread = new Thread(this);
		thread.start();
	}

	@Override
	public final void run() {
		try {
			try {
				checkCancelled();
				execute();
				checkCancelled();
				activate();
				onFinish();
				deactivate();
			} catch (InterruptedException e) {
				try {
					cancelled.set(true);
					activate();
					onCancel();
					deactivate();
				} catch (InterruptedException e1) {
					throw new RuntimeException("interrupted onCancel handling",
							e1);
				}
			} catch (RuntimeException rte) {
				try {
					activate();
					onException(rte);
					deactivate();
				} catch (InterruptedException e1) {
					throw new RuntimeException(
							"interrupted onException handling", e1);
				}
				throw rte;
			}
		} finally {
			try {
				activate();
				onCleanup();
				deactivate();
			} catch (InterruptedException e1) {
				throw new RuntimeException("interrupted onCleanup handling", e1);
			} finally {
				disableServerPushForThisTask();
			}
		}
	}

	private UUID taskId = UUID.randomUUID();

	private void enableServerPushForThisTask() {
		((DesktopCtrl) getDesktop()).enableServerPush(true, taskId);
	}

	private void disableServerPushForThisTask() {
		((DesktopCtrl) getDesktop()).enableServerPush(false, taskId);
	}

	private Desktop getDesktop() {
		return desktopCache.getDesktop(desktopId);
	}

}
